<pre class=metadata>
Title: Native File System
Shortname: native-file-system
Abstract: This document defines a web platform API that lets websites gain write access to the
  native file system. It builds on [[FILE-API]], but adds lots of new functionality on top.
Status: CG-DRAFT
ED: https://wicg.github.io/native-file-system/
Level: 1
Editor: Marijn Kruisselbrink, Google, mek@chromium.org, w3cid 72440
Group: WICG
Repository: wicg/native-file-system
Indent: 2
Complain About: accidental-2119 yes, missing-example-ids yes
Markup Shorthands: css no, markdown yes
</pre>

<pre class=link-defaults>
spec:fetch; type:interface; text:ReadableStream
</pre>

<pre class=anchors>
</pre>

<style>
.domintro dt {
    font-family: Menlo, Consolas, "DejaVu Sans Mono", Monaco, monospace;

    padding-top: 0.5em;
    padding-bottom: 1em;
}
.domintro dt a {
    color: inherit; border-bottom-style: none;
}
.domintro dt code {
    font-size: inherit;
}
</style>

# Introduction # {#introduction}

*This section is non-normative.*

TODO

This provides similar functionality as earlier drafts of the [[file-system-api]] as well as the
[[entries-api]], but with a more modern API.

# Files and Directories # {#files-and-directories}

## Concepts ## {#concepts}

A <dfn>entry</dfn> is either a [=file entry=] or a [=directory entry=].

Each [=/entry=] has an associated <dfn for=entry>name</dfn>.

A <dfn lt="file|file entry">file entry</dfn> additionally consists of <dfn for="file entry">binary
data</dfn> and a <dfn for="file entry">modification timestamp</dfn>.

A <dfn lt="directory|directory entry">directory entry</dfn> additionally consists of a [=/set=] of
<dfn for="directory entry">entries</dfn>. Each member is either a [=file=] or a [=directory=].

Issue: TODO: Explain how entries map to files on disk (multiple entries can map to the same file or
directory on disk but doesn't have to map to any file on disk).

## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}

<xmp class=idl>
dictionary FileSystemHandlePermissionDescriptor {
  boolean writable = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemHandle {
  readonly attribute boolean isFile;
  readonly attribute boolean isDirectory;
  readonly attribute USVString name;

  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
};
</xmp>

A {{FileSystemHandle}} object represents a [=/entry=]. Each {{FileSystemHandle}} object is assocaited
with a <dfn for=FileSystemHandle>entry</dfn> (a [=/entry=]). Multiple separate objects implementing
the {{FileSystemHandle}} interface can all be associated with the same [=/entry=] simultaneously.

<div algorithm="serialization steps">
{{FileSystemHandle}} objects are [=serializable objects=].

Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable.

Their [=serialization steps=], given |value|, |serialized| and |forStorage| are:

1. Set |serialized|.\[[Origin]] to |value|'s [=relevant settings object=]'s [=environment settings object/origin=].
1. TODO

</div>

<div algorithm="deserialization steps">
Their [=deserialization steps=], given |serialized| and |value| are:

1. If |serialized|.\[[Origin]] is not [=same origin=] with
   |value|'s [=relevant settings object=]'s [=environment settings object/origin=],
   then throw a {{DataCloneError}}.
1. TODO

</div>

<div class="note domintro">
  : |handle| . {{FileSystemHandle/isFile}}
  :: Returns true iff |handle| is a {{FileSystemFileHandle}}.

  : |handle| . {{FileSystemHandle/isDirectory}}
  :: Returns true iff |handle| is a {{FileSystemDirectoryHandle}}.

  : |handle| . {{FileSystemHandle/name}}
  :: Returns the [=entry/name=] of the entry represented by |handle|.
</div>

The <dfn attribute for=FileSystemHandle>isFile</dfn> attribute must return true if the associated
[=FileSystemHandle/entry=] is a [=file entry=], and false otherwise.

The <dfn attribute for=FileSystemHandle>isDirectory</dfn> attribute must return true if the
associated [=FileSystemHandle/entry=] is a [=directory entry=], and false otherwise.

The <dfn attribute for=FileSystemHandle>name</dfn> attribute must return the [=entry/name=] of the
associated [=FileSystemHandle/entry=].

### The {{FileSystemHandle/queryPermission()}} method ### {#api-filesystemhandle-querypermission}

Issue: the currently described API here assumes a model where it is not possible to have a
    write-only handle. I.e. it is not possible to have or request write access without also having
    read access. There definitely are use cases for write-only handles (i.e. directory downloads),
    so we might have to reconsider this.

<div class="note domintro">
  : |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/writable}} = false })
  : |status| = await |handle| . {{FileSystemHandle/queryPermission()}}
  :: Queries the current state of the read permission of this handle. If this returns `"prompt"`
     the website will have to call {{FileSystemHandle/requestPermission()}} before any
     operations on the handle can be done. If this returns `"denied"` any operations will reject.

     Usually handles returned by {{chooseFileSystemEntries}} will initially return `"granted"` for
     their read permission state, however other than through the user revoking permission, a handle
     retrieved from IndexedDB is also likely to return `"prompt"`.

  : |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/writable}} = true })
  :: Queries the current state of the write permission of this handle. If this returns `"prompt"`,
     attempting to modify the file or directory this handle represents will require user activation
     and will result in a confirmation prompt being shown to the user. However if the state of the
     read permission of this handle is also `"prompt"` the website will need to call
     {{FileSystemHandle/requestPermission()}}. There is no automatic prompting for read access when
     attempting to read from a file or directory.
</div>

<div algorithm>
The <dfn method for=FileSystemHandle>queryPermission(|descriptor|)</dfn> method, when invoked, must run these steps:

1. TODO

</div>

### The {{FileSystemHandle/requestPermission()}} method ### {#api-filesystemhandle-requestpermission}

<div class="note domintro">
  : |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/writable}} = false })
  : |status| = await |handle| . {{FileSystemHandle/requestPermission()}}
  :: If the state of the read permission of this handle is anything other than `"prompt"`, this
     will return that state directly. If it is `"prompt"` however, user activation is needed and
     this will show a confirmation prompt to the user. The new read permission state is then
     returned, depending on the user's response to the prompt.

  : |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/writable}} = true })
  :: If the state of the write permission of this handle is anything other than `"prompt"`, this
     will return that state directly. If the status of the read permission of this handle is
     `"denied"` this will return that.

     Otherwise the state of the write permission is `"prompt"` and this will show a confirmation
     prompt to the user. The new write permission state is then returned, depending on what the user
     selected.
</div>

<div algorithm>
The <dfn method for=FileSystemHandle>requestPermission(|descriptor|)</dfn> method, when invoked, must run these steps:

1. TODO

</div>

## The {{FileSystemFileHandle}} interface ## {#api-filesystemfilehandle}

<xmp class=idl>
dictionary FileSystemCreateWriterOptions {
  boolean keepExistingData = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemFileHandle : FileSystemHandle {
  Promise<File> getFile();
  Promise<FileSystemWriter> createWriter(optional FileSystemCreateWriterOptions options = {});
};
</xmp>

{{FileSystemFileHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and
[=deserialization steps=] are the same as those for {{FileSystemHandle}}.

Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable.

### The {{FileSystemFileHandle/getFile()}} method ### {#api-filesystemfilehandle-getfile}

<div class="note domintro">
  : |file| = await |fileHandle| . {{FileSystemFileHandle/getFile()}}
  :: Returns a {{File}} representing the state on disk of the entry represented by |handle|.
     If the file on disk changes or is removed after this method is called, the returned
     {{File}} object will likely be no longer readable.
</div>

<div algorithm>
The <dfn method for=FileSystemFileHandle>getFile()</dfn> method, when invoked, must run these steps:

1. TODO

</div>

### The {{FileSystemFileHandle/createWriter()}} method ### {#api-filesystemfilehandle-createwriter}

<div class="note domintro">
  : |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()}}
  : |writer| = await |fileHandle| . {{FileSystemFileHandle/createWriter()|createWriter}}({ {{FileSystemCreateWriterOptions/keepExistingData}}: true/false })
  :: Returns a {{FileSystemWriter}} that can be used to write to the file. Any changes made through
     |writer| won't be reflected in the file represented by |fileHandle| until its
     {{FileSystemWriter/close()}} method is called.
     User agents try to ensure that no partial writes happen, i.e. the file represented by
     |fileHandle| will either contains its old contents or it will contain whatever data was written
     through |writer| up until {{FileSystemWriter/close()}} was called.

     This is typically implemented by writing data to a temporary file, and only replacing the file
     represented by |fileHandle| with the temporary file when the writer is closed.

     If {{FileSystemCreateWriterOptions/keepExistingData}} is `false` or not specified,
     the temporary file starts out empty,
     otherwise the existing file is first copied to this temporary file.
</div>

Issue: There has been some discussion around and desire for a "inPlace" mode for createWriter (where
changes will be written to the actual underlying file as they are written to the writer, for example
to support in-place modification of large files or things like databases). This is not currently
implemented in Chrome. Implementing this is currently blocked on figuring out how to combine the
desire to run malware checks with the desire to let websites make fast in-place modifications to
existing large files.

<div algorithm>
The <dfn method for=FileSystemFileHandle>createWriter(|options|)</dfn> method, when invoked, must run these steps:

1. TODO

</div>

## The {{FileSystemDirectoryHandle}} interface ## {#api-filesystemdirectoryhandle}

<xmp class=idl>
dictionary FileSystemGetFileOptions {
  boolean create = false;
};

dictionary FileSystemGetDirectoryOptions {
  boolean create = false;
};

dictionary FileSystemRemoveOptions {
  boolean recursive = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemDirectoryHandle : FileSystemHandle {
  Promise<FileSystemFileHandle> getFile(USVString name, optional FileSystemGetFileOptions options = {});
  Promise<FileSystemDirectoryHandle> getDirectory(USVString name, optional FileSystemGetDirectoryOptions options = {});

  // This really returns an async iterable, but that is not yet expressable in WebIDL.
  object getEntries();

  Promise<void> removeEntry(USVString name, optional FileSystemRemoveOptions options = {});
};
</xmp>

{{FileSystemDirectoryHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and
[=deserialization steps=] are the same as those for {{FileSystemHandle}}.

Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable.

Issue: Should we have separate getFile and getDirectory methods, or just a single getChild/getEntry
method?

Issue: Having getFile methods in both FileSystemDirectoryHandle and FileSystemFileHandle, but with
very different behavior might be confusing? Perhaps rename at least one of them (but see also
previous issue).

Issue: Should getEntries be its own method, or should FileSystemDirectoryHandle just be an async
iterable itself?

Issue: We will probably want some method to make it possible to compare two handles, and/or determine
if one handle represents a descendant of another handle. Such a method will enable for example an IDE
to detect that the user tries to open a file (through the file picker), where that file actually is
part of the "project" the IDE has open, allowing the IDE to highlight the selected file in a directory
tree.

### The {{FileSystemDirectoryHandle/getFile()}} method ### {#api-filesystemdirectoryhandle-getfile}

<div class="note domintro">
  : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFile()|getFile}}(|name|)
  : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFile()|getFile}}(|name|, { {{FileSystemGetFileOptions/create}}: false })
  :: Returns a handle for a file named |name| in the directory represented by |directoryHandle|. If
     no such file exists, this rejects.

  : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFile()|getFile}}(|name|, { {{FileSystemGetFileOptions/create}}: true })
  :: Returns a handle for a file named |name| in the directory represented by |directoryHandle|. If
     no such file exists, this creates a new file. If no file with named |name| can be created this
     rejects. Creation can fail because there already is a directory with the same name, because the
     name uses characters that aren't supported in file names on the underlying file system, or
     because the user agent for security reasons decided not to allow creation of the file.

     This operation requires write permission, even if the file being returned already exists. If
     this handle doesn't already have write permission, this could result in a prompt being shown to
     the user. To get an existing file without needing write permission, call this method
     with <code>{ {{FileSystemGetFileOptions/create}}: false }</code>.
</div>

<div algorithm>
The <dfn method for=FileSystemDirectoryHandle>getFile(|name|, |options|)</dfn> method, when invoked,
must run these steps:

1. TODO

</div>

### The {{FileSystemDirectoryHandle/getDirectory()}} method ### {#api-filesystemdirectoryhandle-getdirectory}

<div class="note domintro">
  : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectory()|getDirectory}}(|name|)
  : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectory()|getDirectory}}(|name|, { {{FileSystemGetDirectoryOptions/create}}: false })
  :: Returns a handle for a directory named |name| in the directory represented by
    |directoryHandle|. If no such directory exists, this rejects.

  : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectory()|getDirectory}}(|name|, { {{FileSystemGetDirectoryOptions/create}}: true })
  :: Returns a handle for a directory named |name| in the directory represented by
     |directoryHandle|. If no such directory exists, this creates a new directory. If creating the
     directory failed, this rejects. Creation can fail because there already is a file with the same
     name, or because the name uses characters that aren't supported in file names on the underlying
     file system.

     This operation requires write permission, even if the directory being returned already exists.
     If this handle doesn't already have write permission, this could result in a prompt being shown
     to the user. To get an existing directory without needing write permission, call this method
     with <code>{ {{FileSystemGetDirectoryOptions/create}}: false }</code>.
</div>

<div algorithm>
The <dfn method for=FileSystemDirectoryHandle>getDirectory(|name|, |options|)</dfn> method, when
invoked, must run these steps:

1. TODO

</div>

### The {{FileSystemDirectoryHandle/getEntries()}} method ### {#api-filesystemdirectoryhandle-getentries}

<div class="note domintro">
  : for await (const |handle| of |directoryHandle| . {{FileSystemDirectoryHandle/getEntries()}}) {}
  :: Iterates over all entries whose parent is the entry represented by |directoryHandle|.
</div>

<div algorithm>
The <dfn method for=FileSystemDirectoryHandle>getEntries()</dfn> method, when invoked, must run
these steps:

1. TODO

</div>

### The {{FileSystemDirectoryHandle/removeEntry()}} method ### {#api-filesystemdirectoryhandle-removeentry}

<div class="note domintro">
  : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|)
  : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|, { {{FileSystemRemoveOptions/recursive}}: false })
  :: If the directory represented by |directoryHandle| contains a file named |name|, or an empty
     directory named |name|, this will attempt to delete that file or directory.

     Attempting to delete a file or directory that does not exist is considered success,
     while attempting to delete a non-empty directory will result in a promise rejection.

  : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|, { {{FileSystemRemoveOptions/recursive}}: true })
  :: Removes the entry named |name| in the directory represented by |directoryHandle|.
     If that entry is a directory, its contents will also be deleted recursively.
     recursively.

     Attempting to delete a file or directory that does not exist is considered success.
</div>

<div algorithm>
The <dfn method for=FileSystemDirectoryHandle>removeEntry(|name|, |options|)</dfn> method, when invoked, must run
these steps:

1. TODO

</div>

## The {{FileSystemWriter}} interface ## {#api-filesystemwriter}

<xmp class=idl>
[Exposed=(Window,Worker), SecureContext]
interface FileSystemWriter {
  Promise<void> write(unsigned long long position, (BufferSource or Blob or USVString) data);
  Promise<void> truncate(unsigned long long size);
  Promise<void> close();
};
</xmp>

Issue(19): We want some kind of integration with writable streams. One possible option is to make
FileStreamWriter inherit from WritableStream, but other options should be considered as well.

### The {{FileSystemWriter/write()}} method ### {#api-filesystemwriter-write}

<div class="note domintro">
  : await |writer| . {{FileSystemWriter/write()|write}}(|position|, |data|)
  :: Writes the content of |data| into the file associated with |writer| at position |position|.
     If |position| is past the end of the file writing will fail and this method rejects.

     No changes are written to the actual file until on disk until {{FileSystemWriter/close()}}
     is called. Changes are typically written to a temporary file instead.
</div>

<div algorithm>
The <dfn method for=FileSystemWriter>write(|position|, |data|)</dfn> method, when invoked, must run
these steps:

1. TODO

</div>

### The {{FileSystemWriter/truncate()}} method ### {#api-filesystemwriter-truncate}

<div class="note domintro">
  : await |writer| . {{FileSystemWriter/truncate()|truncate}}(|size|)
  :: Resizes the file associated with |writer| to be |size| bytes long. If |size| is larger than
     the current file size this pads the file with zero bytes, otherwise it truncates the file.

     No changes are written to the actual file until on disk until {{FileSystemWriter/close()}}
     is called. Changes are typically written to a temporary file instead.
</div>

<div algorithm>
The <dfn method for=FileSystemWriter>truncate(|size|)</dfn> method, when invoked, must run these
steps:

1. TODO

</div>

### The {{FileSystemWriter/close()}} method ### {#api-filesystemwriter-close}

<div class="note domintro">
  : await |writer| . {{FileSystemWriter/close()}}
  :: First flushes any data written so far to disk, and then closes the writer.
     No changes will be visible in the destination file until this method is called.
     Furthermore, if the file on disk changed between creating this |writer| and this invocation of
     {{FileSystemWriter/close()}}, this will reject and all future operations on the writer will
     fail.

     This operation can take some time to complete, as user agents might use this moment to run
     malware scanners or perform other security checks if the website isn't sufficiently trusted.
</div>

<div algorithm>
The <dfn method for=FileSystemWriter>close()</dfn> method, when invoked, must run these
steps:

1. TODO

</div>

# Accessing native filesystem # {#native-filesystem}

## The {{Window/chooseFileSystemEntries()}} method ## {#api-choosefilesystementries}

<xmp class=idl>
enum ChooseFileSystemEntriesType { "open-file", "save-file", "open-directory" };

dictionary ChooseFileSystemEntriesOptionsAccepts {
  USVString description;
  sequence<USVString> mimeTypes;
  sequence<USVString> extensions;
};

dictionary ChooseFileSystemEntriesOptions {
    ChooseFileSystemEntriesType type = "open-file";
    boolean multiple = false;
    sequence<ChooseFileSystemEntriesOptionsAccepts> accepts;
    boolean excludeAcceptAllOption = false;
};

[SecureContext]
partial interface Window {
    Promise<(FileSystemHandle or sequence<FileSystemHandle>)>
        chooseFileSystemEntries(optional ChooseFileSystemEntriesOptions options = {});
};
</xmp>

<div class="note domintro">
  : |result| = await window . {{Window/chooseFileSystemEntries()|chooseFileSystemEntries}}(|options|)
  :: Shows a file picker dialog to the user and returns handles for the selected files or
     directories.

     The |options| argument sets options that influence the behavior of the shown file picker.

     |options|.{{ChooseFileSystemEntriesOptions/type}} specifies the type of the entry the website
     wants the user to pick.
     When set to {{ChooseFileSystemEntriesType/"open-file"}} (the default), the user can select only
     existing files.
     When set to {{ChooseFileSystemEntriesType/"save-file"}} the dialog will additionally let the
     user select files that don't yet exist, and if the user selects a file that does exist already,
     its contents will be cleared before the handle is returned to the website.
     Finally when set to {{ChooseFileSystemEntriesType/"open-directory"}}, the dialog will let the
     user select directories instead of files.

     If |options|.{{ChooseFileSystemEntriesOptions/multiple}} is false (or absent) the user can
     only select a single file, and the |result| will be a single {{FileSystemHandle}}. If on the
     other hand |options|.{{ChooseFileSystemEntriesOptions/multiple}} is true, the dialog can let
     the user select more than one file, and |result| will be an array of {{FileSystemHandle}}
     instances (even if the user did select a single file, if
     {{ChooseFileSystemEntriesOptions/multiple}} is true this will be returned as a single-element
     array).

     Finally |options|.{{ChooseFileSystemEntriesOptions/accepts}} and
     |options|.{{ChooseFileSystemEntriesOptions/excludeAcceptAllOption}} specify the types of files
     the dialog will let the user select. Each entry in
     |options|.{{ChooseFileSystemEntriesOptions/accepts}} describes a single type of file,
     consisting of a {{ChooseFileSystemEntriesOptionsAccepts/description}}, zero or more
     {{ChooseFileSystemEntriesOptionsAccepts/mimeTypes}} and zero or more
     {{ChooseFileSystemEntriesOptionsAccepts/extensions}}. Options with no valid
     {{ChooseFileSystemEntriesOptionsAccepts/mimeTypes}} and no
     {{ChooseFileSystemEntriesOptionsAccepts/extensions}} are invalid and are ignored. If no
     {{ChooseFileSystemEntriesOptionsAccepts/description}} is provided one will be generated.

     If |options|.{{ChooseFileSystemEntriesOptions/excludeAcceptAllOption}} is true, or if no valid
     entries exist in |options|.{{ChooseFileSystemEntriesOptions/accepts}}, a option matching all
     files will be included in the file types the dialog lets the user select.
</div>

<div algorithm>
The <dfn method for=Window>chooseFileSystemEntries(|options|)</dfn> method, when invoked, must run
these steps:

1. Let |environment| be the [=current settings object=].

1. If |environment|'s [=environment settings object/origin=] is an [=opaque origin=],
   return [=a promise rejected with=] a {{SecurityError}}.

1. Let |browsing context| be |environment|'s [=responsible browsing context=].

1. Let |top-level context| be |browsing context|'s [=top-level browsing context=].

1. If |environment|'s [=environment settings object/origin=] is not [=same origin=] with |browsing context|'s [=top-level browsing context=]'s [=active document=]'s  [=/origin=],
   return [=a promise rejected with=] a {{SecurityError}}.

   Issue: There must be a better way to express this "no third-party iframes" constraint.

1. TODO

</div>

# Accessing special filesystems # {#special-filesystems}

## The {{FileSystemDirectoryHandle/getSystemDirectory()}} method ## {#api-getsystemdirectory}

<xmp class=idl>
enum SystemDirectoryType {
  "sandbox"
};

dictionary GetSystemDirectoryOptions {
  required SystemDirectoryType type;
};

[SecureContext]
partial interface FileSystemDirectoryHandle {
  static Promise<FileSystemDirectoryHandle> getSystemDirectory(GetSystemDirectoryOptions options);
};
</xmp>

<div class="note domintro">
  : |directoryHandle| = {{FileSystemDirectoryHandle}} .
    {{FileSystemDirectoryHandle/getSystemDirectory()|getSystemDirectory}}({
       {{GetSystemDirectoryOptions/type}}: {{SystemDirectoryType/"sandbox"}} })
  :: Returns the sandboxed filesystem.
</div>

Issue(27): getSystemDirectory might not be the best name. Also perhaps should be on Window rather
than on FileSystemDirectoryHandle.

<div algorithm>
The <dfn method for=FileSystemDirectoryHandle>getSystemDirectory(|options|)</dfn> method, when
invoked, must run these steps:

1. Let |environment| be the [=current settings object=].

1. If |environment|'s [=environment settings object/origin=] is an [=opaque origin=],
   return [=a promise rejected with=] a {{SecurityError}}.

1. TODO

</div>

# Privacy Considerations # {#privacy-considerations}

*This section is non-normative.*

This API does not give websites any more read access to data than the existing `<input type=file>`
and `<input type=file webkitdirectory>` APIs already do. Furthermore similarly to those APIs, all
access to files and directories is explicitly gated behind a file or directory picker.

There are however several major privacy risks with this new API:

## Users giving access to more, or more sensitive files than they intended. ## {#privacy-wide-access}

This isn't a new risk with this API, but user agents should try to make sure that users are aware
of what exactly they're giving websites access to. This is particularly important when giving
access to a directory, where it might not be immediately clear to a user just how many files
actually exist in that directory.

A related risk is having a user give access to particularly sensitive data. This
could include some of a user agent's configuration data, network cache or cookie store,
or operating system configuration data such as password files. To protect against this, user agents
are encouraged to restrict which directories a user is allowed to select in a directory picker,
and potentially even restrict which files the user is allowed to select. This will make it much
harder to accidentally give access to a directory that contains particularly sensitive data. Care
must be taken to strike the right balance between restricting what the API can access while still
having the API be useful. After all, this API intentionally lets the user use websites to interact
with some of their most private personal data.

## Websites trying to use this API for tracking. ## {#privacy-tracking}

This API could be used by websites to track the user across clearing browsing
data. This is because, in contrast with existing file access APIs, user agents are
able to grant persistent access to files or directories and can re-prompt. In
combination with the ability to write to files, websites will be able to persist an
identifier on the users' disk. Clearing browsing data will not affect those files
in any way, making these identifiers persist through those actions.

This risk is somewhat mitigated by the fact that clearing browsing data will also clear IndexedDB,
so websites won't have any handles to re-prompt for permission after browsing data was cleared.
Furthermore user agents are encouraged to make it clear what files and directories a website has
access to, and to automatically expire permission grants except for particularly well trusted
origins (for example persistent permissions could be limited to "installed" web applications).

User agents also are encouraged to provide a way for users to revoke permissions granted.
Clearing browsing data is expected to revoke all permissions as well.

## First-party vs third-party contexts. ## {#privacy-third-party}

In third-party contexts (i.e. an iframe whose origin does not match that of the top-level frame)
websites can't gain access to data they don't already have access to. This includes both getting
access to new files or directories via the {{chooseFileSystemEntries}} API, as well as requesting
more permissions to existing handles via the {{requestPermission}} API.

Handles can also only be post-messaged to same-origin destinations. Attempts to send a handle to
a cross-origin destination will result in a {{MessagePort/messageerror}} event.

# Security Considerations # {#security-considerations}

*This section is non-normative.*

This API gives websites the ability to modify existing files on disk, as well as write to new
files. This has a couple of important security considerations:

## Malware ## {#security-malware}

This API could be used by websites to try to store and/or execute malware on the users system.
To mitigate this risk, this API does not provide any way to mark files as executable (on the other
hand files that are already executable likely remain that way, even after the files are modified
through this API). Furthermore user agents are encouraged to apply things like Mark-of-the-Web to
files created or modified by this API.

Finally, user agents are encouraged to verify the contents of files modified by this API via malware
scans and safe browsing checks, unless some kind of external strong trust relation already exists.
This of course has effects on the performance characteristics of this API.

Issue(51): "Atomic writes" attempts to make it explicit what this API can and can't do, and how
performance can be effected by safe browsing checks.

## Ransomware attacks ## {#security-ransomware}

Another risk factor is that of ransomware attacks. The limitations described above regarding
blocking access to certain sensitive directories helps limit the damage such an attack can do.
Additionally user agents can grant write access to files at whatever granularity they deem
appropriate.
